#include "SAW.h"

#include <cstring>
#include <iostream>
using namespace std;

SAW::SAW(unsigned long* stepSizes, size_t amountStepSizes, unsigned int seed) {
    this->directions = 8;
    this->stepSizes = stepSizes;
    this->amountStepSizes = amountStepSizes;
    this->possibleDirections = new bool[directions];
    srand(seed);
}

unsigned long SAW::newStepSize() {
    unsigned long result = stepSizes[stepSizeCounter];
    stepSizeCounter = (stepSizeCounter + 1) % amountStepSizes;
    return result;
}

unsigned long SAW::walk() {
    stepSizeCounter = 0;
    stepSize = newStepSize();
   	long oldX = startX;
    long oldY = startY;
    long oldZ = startZ;
    long oldU = startU;
    map[oldX][oldY][oldZ][oldU] = true;
    long newX, newY, newZ, newU;
    unsigned long steps = 0;

    unsigned long direction;
    unsigned long directions = calculatePossibleDirections(oldX, oldY, oldZ, oldU);
    while (directions != 0) {
        direction = getDirection((rand() % directions));

        if (direction == 0) {
            newX = oldX;
            newY = oldY + stepSize;
            newZ = oldZ;
            newU = oldU;
        } else if (direction == 1) {
            newX = oldX + stepSize;
            newY = oldY;
            newZ = oldZ;
            newU = oldU;
        } else if (direction == 2) {
            newX = oldX;
            newY = oldY - stepSize;
            newZ = oldZ;
            newU = oldU;
        } else if (direction == 3) {
            newX = oldX - stepSize;
            newY = oldY;
            newZ = oldZ;
            newU = oldU;
        } else if (direction == 4) {
            newX = oldX;
            newY = oldY;
            newZ = oldZ + stepSize;
            newU = oldU;
        } else if (direction == 5) {
            newX = oldX;
            newY = oldY;
            newZ = oldZ - stepSize;
            newU = oldU;
        } else if (direction == 6) {
        	newX = oldX;
            newY = oldY;
            newZ = oldZ;
            newU = oldU + stepSize;
        } else if (direction == 7) {
        	newX = oldX;
            newY = oldY;
            newZ = oldZ;
            newU = oldU - stepSize;
        }
        ++steps;
        doStep(oldX, oldY, oldZ, oldU, newX, newY, newZ, newU);
        
        oldX = newX;
        oldY = newY;
        oldZ = newZ;
        oldU = newU;
        directions = calculatePossibleDirections(oldX, oldY, oldZ, oldU);
        
        if(steps > 10000000){
        	map.clear();
        	return 0;
        }
    }
        
    map.clear();
    return steps;
}

void SAW::doStep(long oldX, long oldY, long oldZ, long oldU, long newX, long newY, long newZ, long newU) {
    if (oldX == newX) {
        if (oldY == newY) {
        	if (oldZ == newZ){
        		if(oldU < newU){
        			for (long i = oldU + 1; i <= newU; ++i) {
		                map[oldX][oldY][oldZ][i] = true;
		            }
        		} else {
        			for (long i = newU; i < oldU; ++i) {
		                map[oldX][oldY][oldZ][i] = true;
		            }
        		}
        	} else {
        		if (oldZ < newZ) {
		            for (long i = oldZ + 1; i <= newZ; ++i) {
		                map[oldX][oldY][i][oldU] = true;
		            }
		        } else {
		            for (long i = newZ; i < oldZ; ++i) {
		                map[oldX][oldY][i][oldU] = true;
		            }
		        }
        	}
        } else {
            if (oldY < newY) {
                for (long i = oldY + 1; i <= newY; ++i) {
                    map[oldX][i][oldZ][oldU] = true;
                }
            } else {
                for (long i = newY; i < oldY; ++i) {
                    map[oldX][i][oldZ][oldU] = true;
                }
            }
        }
    } else {
        if (oldX < newX) {
            for (long i = oldX + 1; i <= newX; ++i) {
                map[i][oldY][oldZ][oldU] = true;
            }
        } else {
            for (long i = newX; i < oldX; ++i) {
                map[i][oldY][oldZ][oldU] = true;
            }
        }
    }
    stepSize = newStepSize();
}

bool SAW::canGoNord(long x, long y, long z, long u) {
    for (long i = 1; i <= stepSize; ++i) {
        if (map[x][y + i][z][u]) {
            return false;
        }
    }
    return true;
}

bool SAW::canGoEast(long x, long y, long z, long u) {
    for (long i = 1; i <= stepSize; ++i) {
        if (map[x + i][y][z][u]) {
            return false;
        }
    }
    return true;
}

bool SAW::canGoSouth(long x, long y, long z, long u) {
    for (long i = 1; i <= stepSize; ++i) {
        if (map[x][y - i][z][u]) {
            return false;
        }
    }
    return true;
}

bool SAW::canGoWest(long x, long y, long z, long u) {
    for (long i = 1; i <= stepSize; ++i) {
        if (map[x - i][y][z][u]) {
            return false;
        }
    }
    return true;
}

bool SAW::canGoUp(long x, long y, long z, long u) {
    for (long i = 1; i <= stepSize; ++i) {
        if (map[x][y ][z + i][u]) {
            return false;
        }
    }
    return true;
}

bool SAW::canGoDown(long x, long y, long z, long u) {
    for (long i = 1; i <= stepSize; ++i) {
        if (map[x][y][z - i][u]) {
            return false;
        }
    }
    return true;
}

bool SAW::canGoUp4(long x, long y, long z, long u) {
    for (long i = 1; i <= stepSize; ++i) {
        if (map[x][y ][z][u + i]) {
            return false;
        }
    }
    return true;
}

bool SAW::canGoDown4(long x, long y, long z, long u) {
    for (long i = 1; i <= stepSize; ++i) {
        if (map[x][y][z][u - i]) {
            return false;
        }
    }
    return true;
}

unsigned long SAW::calculatePossibleDirections(long x, long y, long z, long u) {
    unsigned long counter = 0;

    possibleDirections[0] = canGoNord(x, y, z, u);
    possibleDirections[1] = canGoEast(x, y, z, u);
    possibleDirections[2] = canGoSouth(x, y, z, u);
    possibleDirections[3] = canGoWest(x, y, z, u);
    possibleDirections[4] = canGoUp(x, y, z, u);
    possibleDirections[5] = canGoDown(x, y, z, u);
    possibleDirections[6] = canGoUp4(x, y, z, u);
    possibleDirections[7] = canGoDown4(x, y, z, u);

    for (size_t i = 0; i < directions; ++i) {
        if (possibleDirections[i]) {
            ++counter;
        }
    }
    return counter;
}

unsigned long SAW::getDirection(unsigned int number) {
    size_t counter = 0;

    for (size_t i = 0; i < directions; ++i) {
        if (number == counter && possibleDirections[i]) {
            return i;
        }
        if (possibleDirections[i]) {
            ++counter;
        }
    }
}
